<?php
namespace Cola;

use Suikachan\ExceptionHandlers\Boot;

class Cola
{
    /**
     * Singleton instance
     *
     * Marked only as protected to allow extension of the class. To extend,
     * simply override {@link getInstance()}.
     *
     * @var Cola
     */
    protected static $_instance = null;

    /**
     * Object register
     *
     * @var array
     */
    public $reg = array();

    /**
     * Run time config
     *
     * @var \Cola\Config
     */
    protected $config;

    /**
     * Router
     *
     * @var \Cola\Router
     */
    public $router;

    /**
     * Path info
     *
     * @var string
     */
    public $pathInfo;

    /**
     * Dispathc info
     *
     * @var array
     */
    public $dispatchInfo;

    /**
     * Constructor
     *
     */
    protected function __construct()
    {
        Cola::registerAutoload();
    }

    /**
     * Bootstrap
     *
     * @param string $file
     *
     * @return Cola
     * @throws \Exception
     */
    public static function boot($file = LIBRARY_PATH . 'config.php')
    {
        $config = [];
        if (is_string($file) && file_exists($file)) {
            include $file;
        }

        if (!is_array($config)) {
            throw new \Exception('Boot config must be an array or a php config file with variable $config');
        }

        self::getInstance()->config()->merge($config);
        // 注册异常处理类
        (new Boot(self::getInstance()->config()->get("cola.exception", [])))->register();

        return self::$_instance;
    }

    /**
     * Singleton instance
     *
     * @return Cola
     */
    public static function getInstance()
    {
        if (null === self::$_instance) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    public function __invoke()
    {
        return Cola::getInstance();
    }

    /**
     * Set Config
     *
     * @param string $name
     * @param mixed $value
     * @param string $delimiter
     * @return Cola
     */
    public static function setConfig($name, $value, $delimiter = '.')
    {
        self::getInstance()->config()->set($name, $value, $delimiter);
        return self::$_instance;
    }

    /**
     * Get Config
     *
     * @param string $name
     * @param string $default
     * @param string $delimiter
     *
     * @return string|array
     */
    public static function getConfig($name, $default = null, $delimiter = '.')
    {
        return self::getInstance()->config()->get($name, $default, $delimiter);
    }

    /**
     * Set Registry
     *
     * @param string $name
     * @param mixed $obj
     * @return Cola
     */
    public static function setReg($name, $obj)
    {
        self::getInstance()->reg[$name] = $obj;
        return self::$_instance;
    }

    /**
     * Get Registry
     *
     * @param string $name
     * @param mixed $default
     * @return mixed
     */
    public static function getReg($name, $default = null)
    {
        $instance = self::getInstance();
        return isset($instance->reg[$name]) ? $instance->reg[$name] : $default;
    }

    /**
     * Common factory pattern constructor
     *
     * @param string $type
     * @param array $config
     * @return mixed
     */
    public static function factory($type, $config)
    {
        $type = str_replace('_', '\\', $type);

        $adapter = $config['adapter'];
        $class = $type . '\\' . ucfirst($adapter);
        return new $class($config);
    }

    /**
     * Load class
     *
     * @param string $class
     * @param string $file
     * @return boolean
     */
    public static function loadClass($class, $file = '')
    {
    	if (class_exists($class, false) || interface_exists($class, false)) {
            return true;
        }

        if ((!$file)) {
        	$key = "_class.{$class}"; 
        	$file = self::getConfig($key);
        }

        if(is_file($file)){
            include $file;
        }

        return (class_exists($class, false) || interface_exists($class, false)) || self::psr4($class);
    }

    /**
     * User define class path
     *
     * @param string $class
     * @param string $path
     *
     * @return Cola
     */
    public static function setClassPath($class, $path = '')
    {
        if (!is_array($class)) {
            $class = array($class => $path);
        }

        self::getInstance()->config()->merge(array('_class' => $class));

        return self::$_instance;
    }

    /**
     * psr-4 autoloading
     * @param string $class
     * @return boolean
     *
     */
    public static function psr4($class)
    {
        $prefix = $class;
        $psr4 = self::getConfig('_psr4');
        while (false !== ($pos = strrpos($prefix, '\\'))) {
            $prefix = substr($class, 0, $pos);
            $rest = substr($class, $pos + 1);
            if (empty($psr4[$prefix])) continue;
            $file = $psr4[$prefix] . DIRECTORY_SEPARATOR
                  . str_replace('\\', DIRECTORY_SEPARATOR, $rest)
                  . '.php';
            if (file_exists($file)) {
                require_once $file;
                return true;
            }
        }
        return false;
    }

    /**
     * Add psr-4 namespace
     *
     * @param string $prefix
     * @param string $base
     */
    public static function addNamespace($prefix, $base)
    {
        $prefix = trim($prefix, '\\') . '\\';
        $base = rtrim($base, DIRECTORY_SEPARATOR);
        $key = '_psr4';
        $psr4 = self::getConfig($key, []);
        $psr4[$prefix] = $base;
        self::setConfig($key, $psr4);
    }

    /**
     * Register autoload function
     *
     * @param string $func
     * @param boolean $enable
     * @return Cola
     */
    public static function registerAutoload($func = 'static::loadClass', $enable = true)
    {
        $enable ? spl_autoload_register($func) : spl_autoload_unregister($func);
        return self::$_instance;
    }

    /**
     * Get dispatch info
     *
     * @param boolean $init
     * @return array
     */
    public function getDispatchInfo($init = false)
    {
        if ((null === $this->dispatchInfo) && $init) {
            $this->router || ($this->router = new Router());

            if ($urls = self::getConfig('_urls')) {
                $this->router->rules += $urls;
            }

            $this->pathInfo || $this->pathInfo = $_SERVER['PATH_INFO'];
            $this->dispatchInfo = $this->router->match($this->pathInfo);
        }

        return $this->dispatchInfo;
    }

    /**
     * Dispatch
     *
     */
    public function dispatch()
    {
        if (!$di = $this->getDispatchInfo(true)) {
            throw new Exception('No dispatch info found');
        }

        $defaultModuleHome = self::getConfig('_appHome') . '/' . $di['module'];
        $this->config()->setnx('_moduleHome', $defaultModuleHome);
        $this->config()->setnx('_controllersHome', $defaultModuleHome);

        if (isset($di['file']) && file_exists($di['file'])) {
            require_once $di['file'];
        }

        if (isset($di['controller'])) {
            $class = self::parseClass($di['controller'], $this->config()->get('_controller_namespaces' ,[]));
            $controller = new $class;
        }
        //路由前
        /*if(!self::getInstance()->config()->get('pf_config',false)){
            $config = $this->model('channel')->init(PFORM_NAME);
            $config && self::getInstance()->config()->merge($config);
        }*/
        if (isset($di['action'])) {
            $func = isset($controller) ? array($controller, $di['action']) : $di['action'];
            call_user_func_array($func, $di['args']);
        }
    }

    public static function parseClass($name, $namespaces)
    {
        // $namespaces的顺序会影响加载顺序，目前策略为先添加的后加载
//        $namespaces = array_reverse($namespaces);

        foreach ($namespaces as $namespace)
        {
            $class = "$namespace\\$name";
            if(class_exists($class))
            {
                return $class;
            }
        }

        return $name;
    }

    public static function model($name)
    {
        $name = ucfirst(str_replace('Model', '', $name)) . 'Model';

        $class = self::parseClass($name, static::getInstance()->config()->get('_model_namespaces'));

        return new $class;
    }

    /**
     * 为了防止获取到一个未初始化的config
     *
     * @return Config
     *
     */
    public function config()
    {
        if(!$this->config)
        {
            $this->config = new Config();
        }

        return $this->config;
    }
}